﻿Option Infer On

Imports System.Data.OleDb
Imports System.Diagnostics
Imports System.IO
Imports FairShop.Objects
Imports System.Text

Public Class ImportDal

    Public Const LOAD_CHANGES As Integer = 0
    Public Const IMPORTEER_FASE_1 As Integer = 1
    Public Const IMPORTEER_FASE_2 As Integer = 2
    Public Const FIX_DUBBELE_BARCODES As Integer = 3
    Public Const UPDATE_DATA As Integer = 4

    Public Event ToonInfo(ByVal msg As String)
    Public Event ToonError(ByVal msg As String)
    Public Event ToonProgress(ByVal args As ProgressParts)

    Public Structure Aantallen
        Public geimporteerdOmToeTeVoegen As Integer
        Public geimporteerdOmKlaarTeZetten As Integer
        Public geimporteerdEnOngewijzigd As Integer
        Public geimporteerdMetFout As Integer
        Public klaargezetInFase1 As Integer
        Public toegevoegdInFase1 As Integer
        Public bijgewerktInFase2 As Integer
        Public dubbeleBarcodesSamengevoegd As Integer
    End Structure


    Private Progress As ProgressParts

    Public Sub New()
        Progress = New ProgressParts(400, 200, 200, 200, 50)
    End Sub

    Public Function GetImportDataset(ByVal bestandsnaam As String) As ImportDataSet
        Dim prf As New PerformantieHelper("Import: te importeren data inlezen")
        Dim extension As String = Path.GetExtension(bestandsnaam).ToLower()
        Select Case extension
            Case ".xls"
                Dim connectionstring As String = String.Format("Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Extended Properties=""Excel 8.0;HDR=Yes;IMEX=1"";", bestandsnaam)
                Return GetImportDatasetFromExcel(connectionstring)
            Case ".xlsx"
                Dim connectionstring As String = String.Format("Provider=Microsoft.ACE.OLEDB.12.0;Data Source={0};Extended Properties=""Excel 12.0 Xml;HDR=YES;IMEX=1"";", bestandsnaam)
                Return GetImportDatasetFromExcel(connectionstring)
            Case ".tps"
                Return ImportTpsDal.GetImportDataSet(bestandsnaam)
            Case Else
                Throw New NotImplementedException("Importeren van dit bestandsformaat wordt niet ondersteund.")
        End Select
        prf.LogMeting()
    End Function

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="startDatum"></param>
    ''' <param name="importDs"></param>
    ''' <returns>Object met info over de operatie</returns>
    ''' <remarks></remarks>
    Public Function ImporteerVanuitExcelBestand(ByVal startDatum As DateTime, ByVal importDs As ImportDataSet) As Aantallen
        Dim aantallen As New Aantallen()

        If (startDatum >= DateTime.Now()) Then
            ' Deel 2 niet meetellen
            Progress.SetGewicht(IMPORTEER_FASE_2, 0)
        End If

        Dim prf1 As New PerformantieHelper("Import: bestaande data inlezen")

        Dim dsArtikels As ArtikelsDataSet = Utility.Data
        Dim dsUpdates As New UpdatesDataSet()

        Using progressPartRunner As New ProgressPartRunner(Progress, LOAD_CHANGES, importDs._Blad1_.Rows.Count)

            ' sorteer de import op ArtikelCode
            Dim importQuery As IEnumerable(Of ImportDataSet._Blad1_Row) = _
                From a In importDs._Blad1_ _
                Select a _
                Order By a.ArtikelCode

            ' doorloop ook de artikels oplopend volgens ArtikelCode, zodat we niet alles telkens opnieuw moeten doorzoeken
            ' maar zodat we gewoon kunnen verderzoeken van waar we al waren.
            Dim exportQuery As IEnumerable(Of ArtikelsDataSet.ArtikelsRow) = _
                From a In dsArtikels.Artikels _
                Select a _
                Order By a.Artikelcode
            Dim exportList As List(Of ArtikelsDataSet.ArtikelsRow) = exportQuery.ToList()
            prf1.LogMeting()

            Dim prf2 As New PerformantieHelper("Import: artikels in memory bijwerken")
            ' 1/7/2017: dit is het traagste deel, de helft van de tijd
            Dim currentIndex As Integer = 0
            Dim currentArtikelRow As ArtikelsDataSet.ArtikelsRow = exportList(currentIndex)

            For Each importRow As ImportDataSet._Blad1_Row In importQuery
                ' zoek de rij in de bestaande dataset
                While importRow.ArtikelCode.ToUpper() > currentArtikelRow.Artikelcode.ToUpper()
                    currentIndex += 1
                    If (currentIndex >= exportList.Count) Then Exit While
                    currentArtikelRow = exportList(currentIndex)
                End While

                If (importRow.ArtikelCode.ToUpper() = currentArtikelRow.Artikelcode.ToUpper()) Then
                    ' artikel gevonden
                    ' kijk of de artikelcode niet dubbel bestaat; dit blijkt soms voor te komen.
                    Dim bijTeWerkenArtikelRow As ArtikelsDataSet.ArtikelsRow = currentArtikelRow
                    Dim volgendArtikelHeeftDezelfdeCode As Boolean = False
                    If (currentIndex + 1 < exportList.Count) Then
                        ' haal het volgende artikel al op
                        currentIndex += 1
                        currentArtikelRow = exportList(currentIndex)
                        If (importRow.ArtikelCode = currentArtikelRow.Artikelcode) Then
                            volgendArtikelHeeftDezelfdeCode = True
                        End If
                    End If
                    If (volgendArtikelHeeftDezelfdeCode) Then
                        ' artikel komt meerdere keren voor in de bestaande dataset. Log de fout.
                        Dim msg As String = String.Format("Artikel met code {0} komt meerdere keren voor", importRow.ArtikelCode)
                        RaiseEvent ToonInfo(msg)
                        log.WriteError("ImportDal.ImporteerVanuitExcelBestand: " & msg)
                        aantallen.geimporteerdMetFout += 1
                    Else
                        ' artikel gevonden. Voeg het toe aan de updates-dataset.
                        Dim aantal As Integer = ArtikelBijwerken(dsArtikels, dsUpdates, bijTeWerkenArtikelRow, importRow)
                        aantallen.geimporteerdOmKlaarTeZetten += aantal
                        If (aantal = 0) Then aantallen.geimporteerdEnOngewijzigd += 1
                    End If
                Else
                    ' artikel niet gevonden in de bestaande dataset, voeg het toe
                    Dim aantal As Integer = ArtikelToevoegen(dsArtikels, importRow)
                    aantallen.geimporteerdOmToeTeVoegen += aantal
                    If (aantal = 0) Then aantallen.geimporteerdEnOngewijzigd += 1
                End If
                progressPartRunner.Increase()
                RaiseEvent ToonProgress(Progress)
                Application.DoEvents()
            Next
            prf2.LogMeting()
        End Using

        Dim dtNieuweArtikelen As IEnumerable(Of ArtikelsDataSet.ArtikelsRow) = _
            dsArtikels.Artikels.Where(Function(a) a.Id < 0).ToList()
        ' Fout: bij inserts wordt ID bijgewerkt, maar niet in de Utility.Data, maar in een kopie
        'Dim dtNieuweArtikelen As ArtikelsDataSet.ArtikelsDataTable = DirectCast(dsArtikels.Artikels.GetChanges(), ArtikelsDataSet.ArtikelsDataTable)

        ImporteerFase1(dtNieuweArtikelen, dsUpdates.ArtikelUpdates, startDatum, aantallen)

        If (startDatum < DateTime.Now()) Then
            ' als de datum al voorbij is, voer dan de tweede fase ook uit
            RaiseEvent ToonInfo("Datum voor de nieuwe prijzen is al voorbij, bezig prijzen aan te passen...")
            aantallen.bijgewerktInFase2 = ImporteerFase2()
        End If

        ' TODO: progress hier ook tonen
        Dim prf3 As New PerformantieHelper("Import: dubbele barcodes fixen")
        aantallen.dubbeleBarcodesSamengevoegd = FixDubbeleBarcodes()
        prf3.LogMeting()

        Return aantallen
    End Function

    ''' <summary>
    ''' Eerste fase van het importeerproces.
    ''' Kopieert alle wijzigingen in de tabel ArtikelUpdates, en voegt nieuwe artikelen direct toe aan de databank
    ''' </summary>
    ''' <param name="dtNieuweArtikelen">Lijst met nieuwe artikels</param>
    ''' <param name="dtArtikelUpdates">Tabel met updates</param>
    ''' <param name="startDatum">Datum waarop de wijzigingen zullen ingaan.</param>
    ''' <param name="aantallen">Aantallen-object dat verder opgevuld wordt</param>
    Private Sub ImporteerFase1(ByVal dtNieuweArtikelen As IEnumerable(Of ArtikelsDataSet.ArtikelsRow), _
                                      ByVal dtArtikelUpdates As UpdatesDataSet.ArtikelUpdatesDataTable, ByVal startDatum As DateTime, _
                                      ByRef aantallen As Aantallen)
        Using progressPartRunner As New ProgressPartRunner(Progress, IMPORTEER_FASE_1, dtNieuweArtikelen.Count + dtArtikelUpdates.Count)
            If (Not dtNieuweArtikelen.Any AndAlso Not dtArtikelUpdates.Any) Then
                RaiseEvent ToonInfo("Geen wijzigingen te importeren in fase 1")
                Return
            End If
            Dim conn As New OleDbConnection(My.Settings.fairshopConnectionString)
            Try
                Dim prf As New PerformantieHelper("Import: fase1 - wijzigingen kopiëren naar ArtikelUpdates")
                conn.Open()
                For Each row As UpdatesDataSet.ArtikelUpdatesRow In dtArtikelUpdates
                    ' Updates worden in de tabel ArtikelUpdates opgeslagen.
                    ' In de tweede fase worden deze wijzigingen dan gemerged in de echte 
                    ' tabel Artikels op het moment dat de wijzigingen actief worden (1e van de maand).
                    Dim strSQL As String = "INSERT INTO ArtikelUpdates " & _
                        "(Artikelcode, Omschrijving, Barcode, IdCategorie, IdLeverancier, AantalPerVerpakking, Prijs, Inkoopprijs, BTW, Datum) " & _
                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
                    Dim cm As New OleDbCommand(strSQL, conn)
                    ' let op: volgorde moet dezelfde zijn als hierboven
                    DalHelper.VoegParameterToe(cm, row.Artikelcode)
                    DalHelper.VoegParameterToe(cm, row.Omschrijving)
                    DalHelper.VoegParameterToe(cm, row.Barcode)
                    DalHelper.VoegParameterToe(cm, row.IdCategorie)
                    DalHelper.VoegParameterToe(cm, row.IdLeverancier)
                    DalHelper.VoegParameterToe(cm, row.AantalPerVerpakking)
                    DalHelper.VoegParameterToe(cm, row.Prijs)
                    DalHelper.VoegParameterToe(cm, row.Inkoopprijs)
                    DalHelper.VoegParameterToe(cm, row.BTW)
                    DalHelper.VoegParameterToe(cm, startDatum)
                    cm.ExecuteNonQuery()
                    row.AcceptChanges() ' zorg dat het artikel geen updates meer veroorzaakt in de volgende stap
                    aantallen.klaargezetInFase1 += 1
                    progressPartRunner.Increase()
                Next

                ' voeg de toegevoegde artikels direct toe aan de databank, wacht niet tot de 1e van de maand
                If (dtNieuweArtikelen IsNot Nothing) Then
                    Dim aantalToeTeVoegen As Integer = dtNieuweArtikelen.Count
                    RaiseEvent ToonInfo(String.Format("{0} nieuw(e) artikel(s) gevonden om toe te voegen aan de databank", aantalToeTeVoegen))

                    Dim aantalToegevoegd As Integer = 0
                    For Each artikelRij As ArtikelsDataSet.ArtikelsRow In dtNieuweArtikelen
                        Dal.ArtikelToevoegen(artikelRij)
                        aantalToegevoegd += 1
                        progressPartRunner.Increase()
                    Next

                    aantallen.toegevoegdInFase1 = aantalToegevoegd
                    RaiseEvent ToonInfo(String.Format("{0} nieuw(e) artikel(s) direct toegevoegd aan de databank", aantalToegevoegd))
                Else
                    RaiseEvent ToonInfo("Geen nieuwe artikels toe te voegen aan de databank")
                End If
                prf.LogMeting()
            Catch ex As Exception
                ' alleen loggen, exception inslikken
                log.WriteError("ImportDal.ImporteerFase1: " & ex.ToString())
                RaiseEvent ToonError(ex.Message)
            Finally
                conn.Close()
            End Try
        End Using
    End Sub

    ''' <summary>
    ''' Tweede fase van het importeerproces.
    ''' Kopieert alle wijzigingen van de tabel ArtikelUpdates met een datum die voorbij is in de tabel Artikels.
    ''' Deze methode wordt uitgevoerd bij het openen van Fairshop, en 
    ''' na een import indien de datum al voorbij is.
    ''' </summary>
    ''' <returns>Aantal bijgewerkte artikels</returns>
    Public Function ImporteerFase2() As Integer
        Dim conn As New OleDbConnection(My.Settings.fairshopConnectionString)
        Dim aantal As Integer = 0
        Try
            Dim prf As New PerformantieHelper("Import: fase2 - wijzigingen kopiëren van ArtikelUpdates naar Artikels")
            ' haal de updates op uit de tabel ArtikelUpdates
            Dim updatesDataset As New ArtikelsDataSet()

            Dim daLeveranciers As New ArtikelsDataSetTableAdapters.LeveranciersTableAdapter
            daLeveranciers.Fill(updatesDataset.Leveranciers)
            Dim daCategorieen As New ArtikelsDataSetTableAdapters.CategorieenTableAdapter
            daCategorieen.Fill(updatesDataset.Categorieen)

            Dim strSqlUpdates As String = "SELECT Artikelcode, Omschrijving, Barcode, IdCategorie, IdLeverancier, " & _
                "AantalPerVerpakking, Prijs, Inkoopprijs, BTW FROM ArtikelUpdates WHERE Datum < NOW()"
            Dim cmUpdates As New OleDbCommand(strSqlUpdates, conn)
            Dim daUpdateArtikels As New OleDbDataAdapter(cmUpdates)
            daUpdateArtikels.Fill(updatesDataset.Artikels)

            Using progressPartRunner As New ProgressPartRunner(Progress, IMPORTEER_FASE_2, updatesDataset.Artikels.Count)

                If (updatesDataset.Artikels.Count = 0) Then
                    Return 0
                End If

                ' verwerk de updates
                For Each artikelMetNieuweWaarden As ArtikelsDataSet.ArtikelsRow In updatesDataset.Artikels
                    ' zoek de rij in de bestaande dataset
                    Dim artikelCode As String = artikelMetNieuweWaarden.Artikelcode
                    Dim bijTeWerkenArtikel As ArtikelsDataSet.ArtikelsRow = _
                        (From a In Utility.Data.Artikels _
                        Where a.Artikelcode = artikelCode _
                        Select a).FirstOrDefault
                    ' kopieer de nieuwe prijzen van de rij naar het artikel
                    If (bijTeWerkenArtikel IsNot Nothing) Then
                        aantal += ArtikelBijwerkenFase2(bijTeWerkenArtikel, artikelMetNieuweWaarden)
                        progressPartRunner.Increase()
                    End If
                    Application.DoEvents()
                Next
                Dim daArtikels As New ArtikelsDataSetTableAdapters.ArtikelsTableAdapter
                daArtikels.Update(Utility.Data.Artikels)

                ' tabel ArtikelsUpdates opruimen
                conn.Open()
                Dim strSqlClean As String = "DELETE * FROM ArtikelUpdates WHERE Datum < NOW()"
                Dim cmClean As New OleDbCommand(strSqlClean, conn)
                cmClean.ExecuteNonQuery()
            End Using
        Catch ex As Exception
            ' alleen loggen, exception inslikken
            log.WriteError("ImportDal.ImporteerFase2: " & ex.ToString())
            RaiseEvent ToonError(ex.Message)
        Finally
            conn.Close()
        End Try
        Return aantal
    End Function

    ''' <summary>
    ''' Artikel toevoegen vanuit Excel aan de dataset met artikels
    ''' </summary>
    ''' <param name="dsWijzigingen"></param>
    ''' <param name="importRow"></param>
    ''' <returns>Aantal toegevoegde artikels (1 of 0)</returns>
    Private Function ArtikelToevoegen(ByVal dsWijzigingen As ArtikelsDataSet, _
                ByVal importRow As ImportDataSet._Blad1_Row) As Integer
        Dim subcategorie As String = ""
        If (Not importRow.IsSubCategorieNull) Then
            subcategorie = importRow.SubCategorie
        End If
        Dim categorieRow As ArtikelsDataSet.CategorieenRow = Nothing
        If (importRow.IsCategorieNull) Then
            categorieRow = dsWijzigingen.Categorieen.FindById(1) ' ID 1 = Nog niet toegewezen
            log.WriteInfo("Import: bij artikel {0} werd geen categorie gevonden om te importeren, default genomen", importRow.ArtikelCode)
        Else
            categorieRow = VoegCategorieToeIndienNodig(dsWijzigingen, importRow.Categorie, subcategorie)
        End If
        Dim leveranciersRow As ArtikelsDataSet.LeveranciersRow = VoegLeverancierToeIndienNodig(dsWijzigingen, importRow.Leverancier)

        Dim artikelRow As ArtikelsDataSet.ArtikelsRow = dsWijzigingen.Artikels.NewArtikelsRow
        artikelRow.Artikelcode = importRow.ArtikelCode
        artikelRow.Omschrijving = importRow.ArtikelOmschrijving_NL
        If (importRow.IsBarcodeNull()) Then
            artikelRow.Barcode = String.Empty  ' TODO: heeft het zin artikels te importeren zonder barcode?
        Else
            artikelRow.Barcode = importRow.Barcode.Trim() ' soms staan er spaties in de barcodes in Excel
        End If
        artikelRow.AantalPerVerpakking = Convert.ToInt32(importRow.Aantal_per_verpakking)
        artikelRow.LeveranciersRow = leveranciersRow
        artikelRow.CategorieenRow = categorieRow
        artikelRow.Prijs = Utility.ToDecimal(importRow.VP_Incl)
        artikelRow.Inkoopprijs = Utility.ToDecimal(importRow.AP_Excl)
        Dim parseSucces As Boolean = Single.TryParse(importRow.BTW, artikelRow.BTW) ' BTW bevat soms tekst
        If (Not parseSucces) Then artikelRow.BTW = 0
        artikelRow.StatiegeldEuro = 0D ' TODO: dit moet dan manueel achteraf juist gezet worden?
        artikelRow.Verkoopbaar = True
        'artikelRow.DatumToegevoegd = DateTime.Now()
        dsWijzigingen.Artikels.AddArtikelsRow(artikelRow)

        RaiseEvent ToonInfo(String.Format("Artikel '{0}' toegevoegd", importRow.ArtikelOmschrijving_NL))
        Return 1
    End Function

    ''' <summary>
    ''' Kopieer de wijzigingen in het artikel importRow (uit Excel) naar de dataset met updates
    ''' </summary>
    ''' <param name="dsArtikels">Volledige dataset met artikels.</param>
    ''' <param name="dsUpdates">Wijzigingen worden hieraan toegevoegd en deze zullen dan later naar de databank gaan.</param>
    ''' <param name="bijTeWerkenRow">Bij te werken rij. De rij wordt zelf nog niet bijgewerkt, er wordt een record voor toegevoegd aan dsUpdates</param>
    ''' <param name="importRow">Importrij uit Excel met mogelijk nieuwe prijzen voor dat artikel</param>
    ''' <returns>Aantal bijgewerkte artikels (1 of 0)</returns>
    Private Function ArtikelBijwerken(ByVal dsArtikels As ArtikelsDataSet, ByVal dsUpdates As UpdatesDataSet, _
             ByVal bijTeWerkenRow As ArtikelsDataSet.ArtikelsRow, ByVal importRow As ImportDataSet._Blad1_Row) As Integer
        Dim gewijzigd As Boolean = False

        ' Prijs
        ' afronden zodat we altijd met exacte waarden werken (geen afrondingsfouten)
        Dim nieuwePrijs As Decimal = Decimal.Round(Utility.ToDecimal(importRow.VP_Incl), 2)
        ' afronden, want Access bewaart toch maar 3 decimalen voor Currency
        ' anders komt de wijziging elke maand terug (in Access staat bv. 1,109 en in Excel staat 1,108805)
        Dim nieuweInkoopprijs As Decimal = Decimal.Round(Utility.ToDecimal(importRow.AP_Excl), 3)
        If (bijTeWerkenRow.Prijs <> nieuwePrijs OrElse _
            (nieuweInkoopprijs > 0D AndAlso bijTeWerkenRow.Inkoopprijs <> nieuweInkoopprijs)) Then
            gewijzigd = True
        End If

        ' Categorie en subcategorie
        Dim subcategorie As String = ""
        If (Not importRow.IsSubCategorieNull) Then
            subcategorie = importRow.SubCategorie
        End If

        Dim categorieRow As ArtikelsDataSet.CategorieenRow = Nothing
        If (importRow.IsCategorieNull) Then
            categorieRow = dsArtikels.Categorieen.FindById(1) ' ID 1 = Nog niet toegewezen
            log.WriteInfo("Import: bij artikel {0} werd geen categorie gevonden om te importeren, default genomen", importRow.ArtikelCode)
        Else
            categorieRow = VoegCategorieToeIndienNodig(dsArtikels, importRow.Categorie, subcategorie)
        End If

        If (bijTeWerkenRow.CategorieenRow.Categorie <> categorieRow.Categorie OrElse _
            bijTeWerkenRow.CategorieenRow.Subcategorie <> subcategorie) Then
            gewijzigd = True
        End If

        ' Leverancier
        Dim leverancierRow As ArtikelsDataSet.LeveranciersRow = VoegLeverancierToeIndienNodig(dsArtikels, importRow.Leverancier)
        If (bijTeWerkenRow.LeveranciersRow.Naam <> importRow.Leverancier) Then
            gewijzigd = True
        End If

        ' BTW
        Dim importBTW As Single = 0
        If (Not importRow.IsBTWNull AndAlso Single.TryParse(importRow.BTW, importBTW) AndAlso bijTeWerkenRow.BTW <> importBTW) Then
            gewijzigd = True
        End If

        If (gewijzigd) Then
            Dim updateRow As UpdatesDataSet.ArtikelUpdatesRow = dsUpdates.ArtikelUpdates.NewArtikelUpdatesRow()
            updateRow.Artikelcode = importRow.ArtikelCode
            updateRow.Barcode = importRow.Barcode
            updateRow.Prijs = nieuwePrijs
            updateRow.Inkoopprijs = nieuweInkoopprijs
            updateRow.IdCategorie = categorieRow.Id
            updateRow.IdLeverancier = leverancierRow.Id
            updateRow.BTW = importBTW
            updateRow.AantalPerVerpakking = Convert.ToInt32(importRow.Aantal_per_verpakking)
            updateRow.Omschrijving = importRow.ArtikelOmschrijving_NL
            dsUpdates.ArtikelUpdates.AddArtikelUpdatesRow(updateRow)
            RaiseEvent ToonInfo(String.Format("Artikel '{0}' toegevoegd om bij te werken", importRow.ArtikelOmschrijving_NL))
            Return 1
        Else
            Return 0
        End If
    End Function

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="dsWijzigingen">Volledig gevulde dataset om wijzigingen aan toe te voegen</param>
    ''' <param name="categorie">Categorie. Indien ze nog niet in dsWijzigingen zit wordt ze toegevoegd.</param>
    ''' <param name="subcategorie">Subcategorie. Indien ze nog niet in dsWijzigingen zit wordt ze toegevoegd.</param>
    ''' <returns>Rij uit dsWijzigingen met de categorie</returns>
    ''' <remarks></remarks>
    Private Function VoegCategorieToeIndienNodig(ByVal dsWijzigingen As ArtikelsDataSet, _
            ByVal categorie As String, ByVal subcategorie As String) As ArtikelsDataSet.CategorieenRow
        Dim catQuery As IEnumerable(Of ArtikelsDataSet.CategorieenRow) = _
            From c In dsWijzigingen.Categorieen _
            Where c.Categorie = categorie AndAlso c.Subcategorie = subcategorie _
            Select c
        If (Not catQuery.Any) Then
            ' het is een nieuwe categorie
            Dim nieuweCategorieRow As ArtikelsDataSet.CategorieenRow = _
                dsWijzigingen.Categorieen.AddCategorieenRow("", categorie, subcategorie)
            Dal.CategorieToevoegen(nieuweCategorieRow)

            ' volgende regel hoeft niet omdat we op het einde van de import toch alles weer binnenhalen
            'Utility.Data.Categorieen.ImportRow(nieuweCategorieRow) ' ook in globale live dataset bijwerken

            RaiseEvent ToonInfo(String.Format("Categorie '{0} - {1}' toegevoegd", categorie, subcategorie))
        End If
        Return catQuery.First ' moet nu wel bestaan
    End Function

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="dsWijzigingen">Volledig gevulde dataset om wijzigingen aan toe te voegen</param>
    ''' <param name="leverancier">leverancier die indien nodig toegevoegd wordt aan dsWijzigingen</param>
    ''' <returns>Rij uit dsWijzigingen met de leverancier</returns>
    ''' <remarks></remarks>
    Private Function VoegLeverancierToeIndienNodig(ByVal dsWijzigingen As ArtikelsDataSet, _
            ByVal leverancier As String) As ArtikelsDataSet.LeveranciersRow
        Dim levQuery As IEnumerable(Of ArtikelsDataSet.LeveranciersRow) = _
            From l In dsWijzigingen.Leveranciers _
            Where l.Naam = leverancier _
            Select l
        If (Not levQuery.Any) Then
            ' het is een nieuwe categorie
            Dim nieuweLeveranciersRow As ArtikelsDataSet.LeveranciersRow = _
                dsWijzigingen.Leveranciers.AddLeveranciersRow(leverancier)
            Dal.LeverancierToevoegen(nieuweLeveranciersRow)

            ' volgende regel hoeft niet omdat we op het einde van de import toch alles weer binnenhalen
            'Utility.Data.Leveranciers.ImportRow(nieuweLeveranciersRow) ' ook in globale live dataset bijwerken

            RaiseEvent ToonInfo(String.Format("Leverancier '{0}' toegevoegd", leverancier))
        End If
        Return levQuery.First ' moet nu wel bestaan
    End Function

    ''' <summary>
    ''' Werk artikel bij tijdens fase 2
    ''' </summary>
    ''' <param name="bijTeWerkenArtikel">Bij te werken artikel</param>
    ''' <param name="artikelMetNieuweWaarden">Artikel met de nieuwe waarden. 
    ''' Dit artikel zit in een andere dataset.</param>
    ''' <returns>Aantal bijgewerkte artikels (1 of 0)</returns>
    Private Function ArtikelBijwerkenFase2( _
             ByVal bijTeWerkenArtikel As ArtikelsDataSet.ArtikelsRow, ByVal artikelMetNieuweWaarden As ArtikelsDataSet.ArtikelsRow) As Integer
        If (bijTeWerkenArtikel Is Nothing) Then Return 0
        Dim gewijzigd As Boolean = False

        ' Prijs
        Dim nieuwePrijs As Decimal = artikelMetNieuweWaarden.Prijs
        Dim nieuweInkoopprijs As Decimal = artikelMetNieuweWaarden.Inkoopprijs
        If (bijTeWerkenArtikel.Prijs <> nieuwePrijs OrElse _
            (nieuweInkoopprijs > 0D AndAlso bijTeWerkenArtikel.Inkoopprijs <> nieuweInkoopprijs)) Then
            bijTeWerkenArtikel.Prijs = nieuwePrijs
            bijTeWerkenArtikel.Inkoopprijs = nieuweInkoopprijs
            gewijzigd = True
        End If

        ' Categorie/subcategorie
        If (bijTeWerkenArtikel.IdCategorie <> artikelMetNieuweWaarden.IdCategorie) Then
            bijTeWerkenArtikel.IdCategorie = artikelMetNieuweWaarden.IdCategorie
            gewijzigd = True
        End If

        ' Leverancier
        If (bijTeWerkenArtikel.IdLeverancier <> artikelMetNieuweWaarden.IdLeverancier) Then
            bijTeWerkenArtikel.IdLeverancier = artikelMetNieuweWaarden.IdLeverancier
            gewijzigd = True
        End If

        ' BTW 
        If (bijTeWerkenArtikel.BTW <> artikelMetNieuweWaarden.BTW) Then
            bijTeWerkenArtikel.BTW = artikelMetNieuweWaarden.BTW
            gewijzigd = True
        End If

        If (gewijzigd) Then
            'ToonInfo(String.Format("Rij van artikel '{0}' bijgewerkt", importRow.ArtikelOmschrijving))
            log.WriteInfo(String.Format("Artikel '{0}' bijgewerkt vanuit ArtikelUpdates", artikelMetNieuweWaarden.Omschrijving))
            Return 1
        Else
            log.WriteInfo(String.Format("Artikel '{0}' niet bijgewerkt vanuit ArtikelUpdates", artikelMetNieuweWaarden.Omschrijving))
            Return 0
        End If
    End Function

    Public Function FixDubbeleBarcodes() As Integer
        Dim aantal As Integer = 0
        Dim sqlDubbels As String = "SELECT Barcode, Omschrijving, Prijs, StatiegeldEuro, BTW, " & _
            " Id, Artikelcode, Verkoopbaar, Voorraad, VerwachteVoorraad, DatumToegevoegd " & _
            " FROM Artikels " & _
            " WHERE (Artikels.Barcode In (" & _
            "   SELECT Barcode FROM Artikels As Tmp " & _
            "   WHERE Barcode IS NOT NULL AND Barcode <> '' " & _
            "   GROUP BY [Barcode] " & _
            "   HAVING Count(*)>1) " & _
            " ) "
        Dim ds As New ArtikelsDataSet
        Using conn As New OleDbConnection(My.Settings.fairshopConnectionString)
            Try
                conn.Open()

                Dim prf1 As New PerformantieHelper("FixDubbeleBarcodes: ophalen dubbels")
                Dim cm As New OleDbCommand(sqlDubbels, conn)
                Dim da As New OleDbDataAdapter(cm)
                da.Fill(ds.Artikels)
                Dim meervoudigeBarcodes = (From p In ds.Artikels _
                    Group p By Key = p.Barcode Into Group _
                    Select Barcode = Key, Artikels = Group).ToList()

                Dim msgs As String = String.Format("{0} artikels met dubbele barcodes gevonden. Start met verwerking...", meervoudigeBarcodes.Count)
                log.WriteInfo(msgs)
                RaiseEvent ToonInfo(msgs)
                prf1.LogMeting()

                Using progressPartRunner As New ProgressPartRunner(Progress, FIX_DUBBELE_BARCODES, meervoudigeBarcodes.Count)

                    For Each artikelPaar In meervoudigeBarcodes
                        If artikelPaar.Artikels.Count = 2 Then
                            Dim artikel1 As ArtikelsDataSet.ArtikelsRow = artikelPaar.Artikels(0)
                            Dim artikel2 As ArtikelsDataSet.ArtikelsRow = artikelPaar.Artikels(1)
                            Dim artikelToDelete As ArtikelsDataSet.ArtikelsRow = GetArtikelToDelete(artikel1, artikel2)
                            If artikelToDelete Is Nothing Then
                                Dim msg As String = String.Format("Dubbel artikel '{0}' met code {1} en barcode {2} kan niet automatisch opgeruimd worden. Onmogelijk te kiezen.", _
                                                            artikel1.Omschrijving, artikel1.Artikelcode, artikelPaar.Barcode)
                                RaiseEvent ToonError(msg)
                                log.WriteError(msg)
                            Else
                                Dim artikelToKeep As ArtikelsDataSet.ArtikelsRow = artikel1
                                If (artikelToDelete.Id = artikel1.Id) Then artikelToKeep = artikel2
                                Dim msg As String = String.Format("Dubbel artikel '{0}' met barcode {1} opruimen. " & _
                                                                  "Artikel {2} ({3}) wordt verplaatst naar artikel {4} ({5}).", _
                                                                artikel1.Omschrijving, artikel1.Barcode, _
                                                                artikelToDelete.Id, artikelToDelete.Artikelcode, artikelToKeep.Id, artikelToKeep.Artikelcode)
                                RaiseEvent ToonInfo(msg)
                                log.WriteInfo(msg)
                                MergeArtikels(artikelToDelete.Id, artikelToKeep.Id)
                                aantal += 1
                            End If
                        Else
                            Dim msg As String = String.Format("Dubbel artikel '{0}' met barcode {1} komt meer dan 2 keer voor", _
                                                        artikelPaar.Artikels.First.Omschrijving, artikelPaar.Barcode)
                            RaiseEvent ToonError(msg)
                            log.WriteError(msg)
                        End If
                        progressPartRunner.Increase()
                        RaiseEvent ToonProgress(Progress)
                    Next
                End Using
                Using progressPartRunner As New ProgressPartRunner(Progress, UPDATE_DATA, 1)
                    Dal.UpdateData()
                End Using
            Finally
                conn.Close()
            End Try
        End Using

        Dim msge As String = String.Format("{0} artikels met dubbele barcodes werden verwerkt.", aantal)
        log.WriteInfo(msge)

        Return aantal
    End Function

    Public Sub MergeArtikels(ByVal idArtikelSource As Integer, ByVal idArtikelTarget As Integer)
        Dim artikelSource As ArtikelsDataSet.ArtikelsRow = Utility.Data.Artikels.FindById(idArtikelSource)
        Dim artikelTarget As ArtikelsDataSet.ArtikelsRow = Utility.Data.Artikels.FindById(idArtikelTarget)
        Try

            ' voorraden overzetten
            log.WriteInfo(String.Format("Dubbel artikel met barcode '{0}' mergen", artikelSource.Barcode))
            artikelTarget.Voorraad += artikelSource.Voorraad
            artikelTarget.VerwachteVoorraad += artikelSource.VerwachteVoorraad

            artikelSource.Voorraad = 0
            artikelSource.VerwachteVoorraad = 0

            ' verkoopsdetails overzetten
            Dim verkoopsdetails = artikelSource.GetVerkoopsDetailsRows().ToList()
            log.WriteInfo(String.Format("{0} verkoopsdetails van dubbel artikel met barcode '{1}' mergen", _
                                        verkoopsdetails.Count, artikelSource.Barcode))
            For Each v As ArtikelsDataSet.VerkoopsDetailsRow In verkoopsdetails
                v.IdArtikel = artikelTarget.Id
            Next

            ' leveringsdetails overzetten
            Dim leveringsdetails = artikelSource.GetLeveringDetailsRows().ToList()
            log.WriteInfo(String.Format("{0} leveringsdetails van dubbel artikel met barcode '{1}' mergen", _
                                        leveringsdetails.Count, artikelSource.Barcode))
            For Each l As ArtikelsDataSet.LeveringDetailsRow In leveringsdetails
                l.IdArtikel = artikelTarget.Id
            Next

            ' voorraadbewegingen overzetten
            Dim voorraadbewegingen = artikelSource.GetVoorraadbewegingenRows().ToList()
            log.WriteInfo(String.Format("{0} voorraadbewegingen van dubbel artikel met barcode '{1}' mergen", _
                                        voorraadbewegingen.Count, artikelSource.Barcode))
            For Each v As ArtikelsDataSet.VoorraadbewegingenRow In voorraadbewegingen
                v.IdArtikel = artikelTarget.Id
            Next

            ' deelartikels van samengestelde artikels overzetten
            Dim sam1 As List(Of SamengesteldeArtikelsDataSet.SamengesteldeArtikelsRow) = (From sar In Utility.SamengesteldeArtikels _
                                                                                      Where sar.DeelArtikelId = idArtikelSource).ToList()
            log.WriteInfo(String.Format("{0} samengestelde artikels van dubbel artikel met barcode '{1}' mergen", _
                                        sam1.Count, artikelSource.Barcode))
            For Each sa As SamengesteldeArtikelsDataSet.SamengesteldeArtikelsRow In sam1
                sa.DeelArtikelId = idArtikelTarget
            Next

            ' hoofdartikels van samengestelde artikels overzetten
            Dim sam2 As List(Of SamengesteldeArtikelsDataSet.SamengesteldeArtikelsRow) = (From sar In Utility.SamengesteldeArtikels _
                                                                                      Where sar.ArtikelId = idArtikelSource).ToList()
            log.WriteInfo(String.Format("{0} deelartikels van dubbel artikel met barcode '{1}' mergen", _
                                        sam2.Count, artikelSource.Barcode))
            For Each sa As SamengesteldeArtikelsDataSet.SamengesteldeArtikelsRow In sam2
                sa.ArtikelId = idArtikelTarget
            Next

            ' Verkoopbaar afzetten
            artikelTarget.Verkoopbaar = True
            artikelSource.Verkoopbaar = False

            log.WriteInfo(String.Format("Artikel {0} werd onverkoopbaar gezet. Data is overgezet naar artikel {1}", idArtikelSource, idArtikelTarget))
        Catch ex As Exception
            log.WriteError(ex.ToString)
        End Try
    End Sub

    Private Function GetArtikelToDelete(ByVal artikel1 As ArtikelsDataSet.ArtikelsRow, ByVal artikel2 As ArtikelsDataSet.ArtikelsRow) As ArtikelsDataSet.ArtikelsRow
        If (artikel2.Prijs = 0D OrElse artikel1.Prijs / artikel2.Prijs < 0.7D OrElse artikel1.Prijs / artikel2.Prijs > 1.3D) Then
            ' te groot prijsverschil => beter manueel checken vooraleer te mergen
            Return Nothing
        End If
        '' DatumToegevoegd is uit de dataset gehaald
        'If (Not artikel1.IsDatumToegevoegdNull AndAlso Not artikel2.IsDatumToegevoegdNull) Then
        '    If (artikel1.DatumToegevoegd > artikel2.DatumToegevoegd AndAlso artikel1.Verkoopbaar) Then
        '        Return artikel2
        '    ElseIf (artikel1.DatumToegevoegd < artikel2.DatumToegevoegd AndAlso artikel2.Verkoopbaar) Then
        '        Return artikel1
        '    End If
        'End If
        'If (Not artikel1.IsDatumToegevoegdNull AndAlso artikel2.IsDatumToegevoegdNull AndAlso artikel1.Verkoopbaar) Then
        '    Return artikel2
        'End If
        'If (Not artikel2.IsDatumToegevoegdNull AndAlso artikel1.IsDatumToegevoegdNull AndAlso artikel2.Verkoopbaar) Then
        '    Return artikel1
        'End If
        If (artikel1.Id > artikel2.Id AndAlso artikel1.Verkoopbaar) Then
            Return artikel2
        ElseIf (artikel1.Id < artikel2.Id AndAlso artikel2.Verkoopbaar) Then
            Return artikel1
        End If
        If (artikel1.Verkoopbaar And Not artikel2.Verkoopbaar) Then
            Return artikel2
        End If
        If (artikel2.Verkoopbaar And Not artikel1.Verkoopbaar) Then
            Return artikel1
        End If
        Return Nothing
    End Function

    Private Function GetImportDatasetFromExcel(ByVal connectionstring As String) As ImportDataSet
        Dim connection As New OleDbConnection(connectionstring)
        Dim importDs As New ImportDataSet()
        Try
            Dim adapter As New ImportDataSetTableAdapters.Blad1_TableAdapter()
            adapter.Connection = connection
            adapter.Fill(importDs._Blad1_)
            Return importDs
        Catch ex As Exception
            log.WriteError("ImportDal.GetImportDatasetFromExcel" & Environment.NewLine & ex.ToString())
            RaiseEvent ToonError("Fout tijdens het importeren uit Excel - verkeerd formaat")
            RaiseEvent ToonError(ex.Message)
            Return Nothing
        End Try
    End Function

End Class
